﻿open System
open System.Globalization
open System.IO

type PackageVersion(package, version: DateTime) =
    member this.Package = package
    member this.Version = version
    
    interface IFormattable with
        member this.ToString(format, provider) =
            this.ToString(format, provider)
    
    override this.ToString() =
        sprintf "%A-%A" this.Package this.Version
    
    member this.ToString(provider) =
        this.ToString(null, provider)
    
    member this.ToString(format, provider: IFormatProvider) =
        package + "-" + version.ToString(provider)
    
    static member FromString(source: string, provider) =
        let dateStart = source.IndexOf('-')
        if dateStart < 0 then
            FormatException() |> raise
        else
            let package = source.Substring(0, dateStart)
            let version = DateTime.Parse(source.Substring(dateStart + 1), provider)
            PackageVersion(package, version)

type PackageSystemState(versions) =
    let versions = List.of_seq versions
    let state =
        versions
        |> List.map (fun (version: PackageVersion) -> version.Package, version)
        |> Map.of_seq
    
    member this.List = versions
    member this.Map = state
    
    member this.GetVersion(package) =
        Map.tryFind package state
    
    interface IFormattable with
        member this.ToString(format, provider) =
            let result = Text.StringBuilder()
            for version in versions do
                result.AppendLine(version.ToString(provider)) |> ignore
            
            result.ToString()
    
    override this.ToString() =
        let result = Text.StringBuilder()
        for version in versions do
            result.AppendFormat("{0}\n", version) |> ignore
        
        result.ToString()
    
    static member FromString(source: string, provider) =
        source.Split('\n')
        |> Array.map (fun str -> PackageVersion.FromString(str, provider))
        |> fun x -> PackageSystemState x

type PackageDependencyInfo(isVirtual: bool, packages: PackageVersion list)=
    member this.IsVirtual = isVirtual
    member this.Packages = packages

type PackageDependencyGraph(infos: Map<PackageVersion, PackageDependencyInfo>) =
    member this.GetInfo(version) =
        Map.tryFind version infos
    
    member this.Map = infos


type PackageManager =
    static member packageFileLock = "fpkg.lock"
    static member sourcesFileLock = "pkgsource.lock"
    
    static member private computeInstall packages =
        []
    
    static member private ComputeInstall packages =
        []
    
    [<OverloadID("private install")>]
    static member private Install toInstall =
        [], None
    
    [<OverloadID("public install")>]
    static member Install (packages, onerror) =
        use sourcesLock = new FileLock(PackageManager.sourcesFileLock, readonly = true)
        use packageLock = new FileLock(PackageManager.packageFileLock, readonly = false)
        let install = PackageManager.computeInstall packages
        let ``done``, error = PackageManager.Install install
        match error with
        | Some(error)->
            let keep = onerror error
            if not keep then
                PackageManager.Uninstall ``done``
            else
                NotImplementedException("keep") |> raise
        | None ->
            NotImplementedException("compute state after install") |> raise
    
    static member private Uninstall toUninstall =
        NotImplementedException("uninstall and its state") |> raise
